<script>
export default {
  name: 'VirtualTable',
  data() {
    return {
      title: '虚拟表格',

      tableData: [],
      loadData: [],
      ticking: false,
      startIndex: 0,
      endIndex: 9,
      itemHeight: 47.8,
      bodyWrapper: null,
      fixedLeftBodyEl: null,
      rightFixedBodyEl: null,
      bodyEl: null,
      viewporOutsideCount: 2,
      buffer: 0,
    }
  },
  created() {
    const loading = ElLoading.service()
    setTimeout(() => {
      loading.close()

      for (let i = 0; i < 2000; i++) {
        this.tableData.push({
          id: i + 1,
          index: i + 1,
          date: '2016-05-03',
          name: '张三',
          province: '黑龙江',
          city: '哈尔滨',
          address: '南岗区-哈尔滨大街501号',
          zip: '000000',
          remark: i + 1,
        })
      }

      this.loadData = this.tableData.slice(0, 10)

      this.$nextTick(() => {
        const bodyRefs = this.$refs.table.$refs
        this.bodyEl = bodyRefs.bodyWrapper.firstElementChild
        this.fixedLeftBodyEl = bodyRefs.fixedBodyWrapper.firstElementChild
        this.rightFixedBodyEl = bodyRefs.rightFixedBodyWrapper.firstElementChild

        this.bodyEl.style.paddingTop = this.startIndex * this.itemHeight + 'px'
        this.bodyEl.style.paddingBottom =
          (this.tableData.length - this.endIndex - 1) * this.itemHeight + 'px'

        this.fixedLeftBodyEl.style.paddingTop = this.bodyEl.style.paddingTop
        this.fixedLeftBodyEl.style.paddingBottom =
          this.bodyEl.style.paddingBottom

        this.rightFixedBodyEl.style.paddingTop = this.bodyEl.style.paddingTop
        this.rightFixedBodyEl.style.paddingBottom =
          this.bodyEl.style.paddingBottom
      })
    }, 500)
  },
  mounted() {
    this.$nextTick(() => {
      this.bodyWrapper = this.$refs.table.$refs.bodyWrapper
      this.bodyWrapper.addEventListener('scroll', this.scrollHandler)
    })
  },
  methods: {
    scrollHandler() {
      if (this.ticking) return
      this.ticking = true
      requestAnimationFrame(() => {
        this.ticking = false
      })

      this.updateLoadData()
    },

    updateLoadData() {
      if (!this.isUpdateLoadData()) return

      const newStartIndex = this.getStartIndex()

      if (newStartIndex === this.startIndex) return

      this.startIndex = newStartIndex
      this.endIndex = this.getEndIndex()

      this.loadData = this.tableData.slice(this.startIndex, this.endIndex + 1)

      this.bodyEl.style.paddingTop = this.startIndex * this.itemHeight + 'px'
      this.bodyEl.style.paddingBottom =
        (this.tableData.length - this.endIndex - 1) * this.itemHeight + 'px'

      this.fixedLeftBodyEl.style.paddingTop = this.bodyEl.style.paddingTop
      this.fixedLeftBodyEl.style.paddingBottom = this.bodyEl.style.paddingBottom

      this.rightFixedBodyEl.style.paddingTop = this.bodyEl.style.paddingTop
      this.rightFixedBodyEl.style.paddingBottom =
        this.bodyEl.style.paddingBottom
    },

    isUpdateLoadData() {
      const scrollTop = this.bodyWrapper.scrollTop
      const topDistance = scrollTop - this.startIndex * this.itemHeight
      const bottomDistance =
        (this.endIndex + 1) * this.itemHeight -
        scrollTop -
        this.bodyWrapper.clientHeight

      // 缓冲距离
      const bufferDistance = this.buffer * this.itemHeight

      // 视口超出列表上下的缓冲距离，需要更新列表
      if (topDistance < bufferDistance || bottomDistance < bufferDistance) {
        return true
      }
      return false
    },

    getStartIndex() {
      const scrollTop = this.bodyWrapper.scrollTop
      if (scrollTop === 0) return 0

      let startIndex =
        Math.floor(scrollTop / this.itemHeight) -
        Math.floor(this.viewporOutsideCount / 2)

      const maxStartIndex = this.tableData.length - this.loadData.length
      if (startIndex < 0) {
        startIndex = 0
      } else if (startIndex > maxStartIndex) {
        startIndex = maxStartIndex
      }

      return startIndex
    },

    getEndIndex() {
      let endIndex = this.startIndex + this.loadData.length - 1
      const maxEndIndex = this.tableData.length - 1
      if (endIndex > maxEndIndex) {
        endIndex = maxEndIndex
      }
      return endIndex
    },
  },
}
</script>

<template>
  <div>
    <h2>{{ title }}</h2>

    <div>
      <el-table ref="table" :data="loadData" border stripe :max-height="400">
        <el-table-column prop="index" label="序号" width="80" fixed />

        <el-table-column prop="date" label="日期" min-width="140" />

        <el-table-column prop="name" label="名字" min-width="120" />

        <el-table-column prop="province" label="省份" min-width="120" />

        <el-table-column prop="city" label="城市" min-width="120" />

        <el-table-column prop="address" label="地址" min-width="180" />

        <el-table-column prop="zip" label="邮编" min-width="120" />

        <el-table-column prop="remark" label="备注" min-width="180" />

        <el-table-column label="操作" min-width="100" fixed="right">
          <template #default="{ row }"> 详情{{ row.remark }} </template>
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>

<style scoped></style>
